//
// Created by Administrator on 2015/6/3.
//
#pragma once

#include <iostream>
#include <cstdint>
#include <cstdio>
#include <cstring>
#include <vector>
#include <map>

#ifdef _MSC_VER
#define snprintf _snprintf
#endif


using namespace std;


class CData
{
    public:
        enum ValueType
        {
            TYPE_EMPTY,
            TYPE_BOOL,
            TYPE_CHAR,
            TYPE_INT16,
            TYPE_INT32,
            TYPE_INT64,
            TYPE_UINT16,
            TYPE_UINT32,
            TYPE_UINT64,
            TYPE_FLOAT,
            TYPE_DOUBLE,
            TYPE_MSTR,   //modifiable str.
            TYPE_CSTR,   //const str.
            TYPE_NSTR,   //self managed str.
            TYPE_ARRAY,
            TYPE_MAP,
        };

        CData() = default;
        CData(const CData& ) = delete;
        CData& operator=(const CData&) = delete;
        CData(CData&& data)
        {
            free();
            this->m_type = data.m_type;
            data.m_type = TYPE_EMPTY;
            this->d_value = data.d_value;
        }

        CData& operator=(CData&& data)
        {
            free();
            this->m_type = data.m_type;
            data.m_type = TYPE_EMPTY;
            this->d_value = data.d_value;
            return *this;
        }

        size_t size() const
        {
            if(this->m_type == TYPE_ARRAY)
            {
                return this->arr_data->size();
            }
            else if(this->m_type == TYPE_MAP)
            {
                return this->map_data->size();
            }
            else //error usage.
                return 0;
        }

        inline bool has_data() const
        {
            return this->m_type != TYPE_EMPTY;
        }

        bool has_key(size_t index) const
        {
            return this-> m_type == TYPE_ARRAY && this->arr_data->size() > index;
        }

        bool has_key(const string& key) const
        {
            return this->m_type == TYPE_MAP && this->map_data->find(key) != this->map_data->end();
        }


        string to_string() const
        {
            char buffer[1024] = {0};
            switch(m_type)
            {
                case TYPE_EMPTY:
                    snprintf(buffer, sizeof(buffer), "empty(nil)");
                    break;
                case TYPE_BOOL:
                    snprintf(buffer, sizeof(buffer), "bool(%d)", b_value);
                    break;
                case TYPE_CHAR:
                    snprintf(buffer, sizeof(buffer), "char(%c)", c_value);
                    break;
                case TYPE_INT16:
                    snprintf(buffer, sizeof(buffer), "int16(%d)", i16_value);
                    break;
                case TYPE_INT32:
                    snprintf(buffer, sizeof(buffer), "int32(%d)", i32_value);
                    break;
                case TYPE_INT64:
                    snprintf(buffer, sizeof(buffer), "int64(%lld)", i64_value);
                    break;
                case TYPE_UINT16:
                    snprintf(buffer, sizeof(buffer), "uint16(%u)", u16_value);
                    break;
                case TYPE_UINT32:
                    snprintf(buffer, sizeof(buffer), "uint32(%u)", u32_value);
                    break;
                case TYPE_UINT64:
                    snprintf(buffer, sizeof(buffer), "uint64(%llu)", u64_value);
                    break;
                case TYPE_FLOAT:
                    snprintf(buffer, sizeof(buffer), "float(%f)", f_value);
                    break;
                case TYPE_DOUBLE:
                    snprintf(buffer, sizeof(buffer), "double(%lf)", d_value);
                    break;
                case TYPE_CSTR:
                case TYPE_MSTR:
                case TYPE_NSTR:
                    snprintf(buffer, sizeof(buffer), "str(%s)", str_value);
                    break;
                case TYPE_ARRAY:
                    {
                        string temp;
                        for(const auto& it : *(arr_data))
                        {
                            temp += (", " + it->to_string());
                        }
                        const char* inner_str = temp.c_str();
                        if(inner_str[0] == ',') inner_str+=2;
                        snprintf(buffer, sizeof(buffer), "array[%s]", inner_str);
                    }
                    break;
                case TYPE_MAP:
                    {
                        string temp;
                        for(const auto& it : *(map_data))
                        {
                            temp += (", " + it.first + ":" + it.second->to_string());
                        }
                        const char* inner_str = temp.c_str();
                        if(inner_str[0] == ',') inner_str+=2;
                        snprintf(buffer, sizeof(buffer), "map{%s}", inner_str);
                    }
                    break;
                default:
                    snprintf(buffer, sizeof(buffer), "unknown(nil)");
                    break;
            }
            return buffer;
        }

        string to_json()
        {
            char buffer[1024] = {0};
            switch(m_type)
            {
                case TYPE_EMPTY:
                    snprintf(buffer, sizeof(buffer), "nil");
                    break;
                case TYPE_BOOL:
                    snprintf(buffer, sizeof(buffer), "%s", b_value ? "true":"false");
                    break;
                case TYPE_CHAR:
                    snprintf(buffer, sizeof(buffer), "\"%c\"", c_value);
                    break;
                case TYPE_INT16:
                    snprintf(buffer, sizeof(buffer), "%d", i16_value);
                    break;
                case TYPE_INT32:
                    snprintf(buffer, sizeof(buffer), "%d", i32_value);
                    break;
                case TYPE_INT64:
                    snprintf(buffer, sizeof(buffer), "%lld", i64_value);
                    break;
                case TYPE_UINT16:
                    snprintf(buffer, sizeof(buffer), "%u", u16_value);
                    break;
                case TYPE_UINT32:
                    snprintf(buffer, sizeof(buffer), "%u", u32_value);
                    break;
                case TYPE_UINT64:
                    snprintf(buffer, sizeof(buffer), "%llu", u64_value);
                    break;
                case TYPE_FLOAT:
                    snprintf(buffer, sizeof(buffer), "%f", f_value);
                    break;
                case TYPE_DOUBLE:
                    snprintf(buffer, sizeof(buffer), "%lf", d_value);
                    break;
                case TYPE_CSTR:
                case TYPE_MSTR:
                case TYPE_NSTR:
                    snprintf(buffer, sizeof(buffer), "\"%s\"", str_value);
                    break;
                case TYPE_ARRAY:
                    {
                        string temp;
                        for(const auto& it : *(arr_data))
                        {
                            temp += (", " + it->to_json());
                        }
                        const char* inner_str = temp.c_str();
                        if(inner_str[0] == ',') inner_str+=2;
                        snprintf(buffer, sizeof(buffer), "[%s]", inner_str);
                    }
                    break;
                case TYPE_MAP:
                    {
                        string temp;
                        for(const auto& it : *(map_data))
                        {
                            temp += (", \"" + it.first + "\":" + it.second->to_json());
                        }
                        const char* inner_str = temp.c_str();
                        if(inner_str[0] == ',') inner_str+=2;
                        snprintf(buffer, sizeof(buffer), "{%s}", inner_str);
                    }
                    break;
                default:
                    snprintf(buffer, sizeof(buffer), "unknown(nil)");
                    break;
            }
            return buffer;
        }


        template<typename T>
            CData& operator = (const T& t)
            {
                m_type = TYPE_EMPTY;
                return *this;
            }
        CData& operator = (char* t);
        CData& operator = (const char* t);


        friend ostream& operator <<(ostream& os, const CData& data)
        {
            os<<data.to_string().c_str();
            return os;
        }


        void free()
        {
            if(m_type == TYPE_ARRAY)
            {
                for(auto& it : *(arr_data))
                {
                    it->free();
                    delete it;
                }
                delete arr_data;
                arr_data = NULL;
                m_type = TYPE_EMPTY;
            }
            else if(m_type == TYPE_MAP)
            {
                for(auto& it : *(map_data))
                {
                    it.second->free();
                    delete it.second;
                }
                delete map_data;
                map_data = NULL;
                m_type = TYPE_EMPTY;
            }
        }


        ~CData()
        {
            free();
        }


        void malloc()
        {
            if(m_type == TYPE_ARRAY)
            {
                arr_data = new vector<CData*>;
            }
            else if(m_type == TYPE_MAP)
            {
                map_data = new map<string, CData*>;
            }
        }


        CData& operator [](const size_t& index)
        {
            if(m_type != TYPE_ARRAY)
            {
                free();
                m_type = TYPE_ARRAY;
                malloc();
            }
            if(arr_data->size() > index) return *(*(arr_data))[index];
            else
            {
                CData* item = new CData;
                arr_data->resize(index+1);
                (*(arr_data))[index] = item;
                return *item;
            }
        }

        CData& operator[](const string& key)
        {
            if(m_type != TYPE_MAP)
            {
                free();
                m_type = TYPE_MAP;
                malloc();
            }
            auto it = map_data->find(key);
            if(it == map_data->end())
            {
                CData* item = new CData;
                (*map_data)[key] = item;
                return *item;
            }
            return *(it->second);
        }




    private:
        union
        {
            bool     b_value;
            char     c_value;
            int16_t  i16_value;
            uint16_t u16_value;
            int32_t  i32_value;
            int      i_value;
            uint32_t u32_value;
            int64_t  i64_value;
            uint64_t u64_value;
            char*    str_value;
            float    f_value;
            double   d_value;
            vector<CData*>* arr_data;
            map<string, CData*>*map_data;


        };


        ValueType m_type = TYPE_EMPTY;

    public:
        void deep_copy(const CData& data)
        {
            free();
            m_type = data.m_type;
            malloc();
            if(m_type == TYPE_ARRAY)
            {
                for(size_t index = 0; index < data.arr_data->size(); ++index)
                {
                    operator[](index).deep_copy((*(*(data.arr_data))[index]));
                }
            }
            else if(m_type == TYPE_MAP)
            {
                for(const auto& it : *(data.map_data))
                {
                    operator[](it.first).deep_copy(*(it.second));
                }
            }
            else
            {
                this->d_value = data.d_value;
            }
        }

};


    template<>
CData& CData::operator = (const bool& t)
{
    m_type = TYPE_BOOL;
    this->b_value = t;
    return *this;
}
    template<>
CData& CData::operator = (const char& t)
{
    m_type = TYPE_CHAR;
    this->c_value = t;
    return *this;
}


    template<>
CData& CData::operator = (const int16_t& t)
{
    m_type = TYPE_INT16;
    this->i16_value = t;
    return *this;
}
    template<>
CData& CData::operator = (const int32_t& t)
{
    m_type = TYPE_INT32;
    this->i32_value = t;
    return *this;
}
    template<>
CData& CData::operator = (const int64_t& t)
{
    m_type = TYPE_INT64;
    this->i64_value = t;
    return *this;
}
    template<>
CData& CData::operator = (const uint16_t& t)
{
    m_type = TYPE_UINT16;
    this->u16_value = t;
    return *this;
}
    template<>
CData& CData::operator = (const uint32_t& t)
{
    m_type = TYPE_UINT32;
    this->u32_value = t;
    return *this;
}
    template<>
CData& CData::operator = (const uint64_t& t)
{
    m_type = TYPE_UINT64;
    this->u64_value = t;
    return *this;
}
    template<>
CData& CData::operator = (const float& t)
{
    m_type = TYPE_FLOAT;
    this->f_value = t;
    return *this;
}
    template<>
CData& CData::operator = (const double& t)
{
    m_type = TYPE_DOUBLE;
    this->d_value = t;
    return *this;
}


CData& CData::operator = (char* t)
{
    m_type = TYPE_MSTR;
    this->str_value = t;
    return *this;
}


CData& CData::operator = (const char* t)
{
    m_type = TYPE_CSTR;
    this->str_value = const_cast<char*>(t);
    return *this;
}
